$PSDefaultParameterValues['*:Encoding'] = 'utf8'

#检查是否管理员权限运行脚本
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
#if((New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) -eq $false){
#    Write-Host "请使用管理员权限运行此脚本, 脚本将退出运行." -ForegroundColor Red
#    Break script
#}

#检测是否联网
# if ((Test-NetConnection "baidu.com" -WarningAction SilentlyContinue).PingSucceeded -ne $true){
#     Write-Host "本机当前无法访问网络, 请连接网络后重试. 脚本将退出运行." -ForegroundColor Red
#     Break script
# }

######################################################################################################
#安装目录
$dirRoot = (Get-Location).Path

#是否能够访问github.com, 以此来判断是否需要代理
try{
    $isGithub = Invoke-WebRequest -Uri "https://github.com" -Method Head
    $isGithub = $isGithub -is [object] -and $isGithub.StatusDescription -eq "OK"
}catch{
    $isGithub = $false
}

#$isGithub = $false

#oracle.com mysql.com  网站在某些环境下不能获取网页内容, 此时设置为$true使用代理, 如果网络没有问题则设置为$false
$isOracle = $false

#默认php版本
$defaultPhpVersion = "8.2"
$curlCaFile = "curl-cacert.pem"

#代理网址 file=>下载文件&save=保存文件名, url=>获取网页源码 
$proxyUrl = "https://utilsshark.tentuu.com/proxyurl/"
###*****************************************************###
#反向代理, mongodb php jdk phpbuild  
#$proxyPass = "https://utilsshark.tentuu.com/"
###curl.exe -A "Mozilla google" -SfLo apacheTomcat3.txt "https://cloud.tencent.com/developer/article/2201616?areaId=106001"
###*****************************************************###


#gitee上下载固定的编译好的dll和exe文件  ,https://gitee.com/luinfy/deps/raw/master/7z/7z.dll
$staticUrl = "https://gitee.com/luinfy/deps/raw/master/"

#删除编译链接文件
$deleteDevelFileType = @("pdb", "lib")
$deletePgAdmin = $true

#pip mirrors
$pipMirrorUrl = "https://mirrors.aliyun.com/pypi/simple/"

###*****************************************************###
#不再支持旧版本扩展的自动安装
#windows.php.net 是否提供pecl扩展的dll编译版本
#$peclDllStatus = $false
#php扩展
#$peclExtension = ("redis", "igbinary", "memcached", "mongodb", "ssh2")
#windows.php.net 上支持的最新php版本的扩展
#$peclArchiveLatestVersion = "8.0"
###*****************************************************###


#环境变量
$userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/120.0.0.0"
$today = (Get-Date -Format "yyyyMMdd").ToString()
$now = (Get-Date -Format "yyyyMMdHHmmss").ToString()
$ips=([net.dns]::GetHostAddresses('') | Select-Object -ExpandProperty IPAddressToString)


#输出颜色变量
$colorSuccess = "Green"
$colorWarning = "Magenta"
$colorError = "Red"
$colorNormal = "DarkYellow"
$colorOfficial = "DarkGreen"
$colorThird = "DarkMagenta"
$colorProgress = "DarkCyan"
$colorProgressRemainder = "Gray"
$echoTab = "    "
#进度条长度
$echoProgressLength = 70
#检查下载进度间隔 秒
$checkDownloadSecond = 3


#开发环境的路径
$dirInited = @(
    "bin", "sbin", "etc", "local", "share", "usr/www", "usr/script", "usr/cgi-bin", "etc/ssl/sites",
    "var/log", "var/run", "var/db", "var/backup/etc", "var/version",
    "tmp/download/cache", "tmp/unzip"
)
foreach($dr in $dirInited){
    $dr = Join-Path $dirRoot $dr
    if(!(Test-Path $dr)){
        New-Item -Path $dr -ItemType Directory | Out-Null
    }
}
$dirUserHome = Join-Path $env:HOMEDRIVE $env:HOMEPATH
$dirLocal = Join-Path $dirRoot "local"
$dirShare = Join-Path $dirRoot "share"
$dirBin = Join-Path $dirRoot "bin"
$dirConfig = Join-Path $dirRoot "etc"
$dirTemp = Join-Path $dirRoot "tmp"
$dirDownload = Join-Path $dirTemp "download"
$dirCache = Join-Path $dirDownload "cache"
$dirUnzip = Join-Path $dirTemp "unzip"
$dirDb = Join-Path $dirRoot "var/db"
$dirLog = Join-Path $dirRoot "var/log"
$dirRun = Join-Path $dirRoot "var/run"
$dirVersion = Join-Path $dirRoot "var/version"
$dirConfigBackup = Join-Path $dirRoot "var/backup/etc"


#后台下载进程
$downloadAsync = @{}
#已安装的服务
$installedService = @{}
$allService = Get-Service -ErrorAction Ignore -WarningAction Ignore
foreach($app in $appData.Keys){
    $app = $app.ToLower()
    foreach($service in $allService){
        $serverName = $service.Name.ToString().ToLower()
        $serverStatus = $service.Status.ToString().ToLower()
        if($serverName.Contains($app)){
            $installedService.$serverName = $serverStatus
        }
    }
}

######################################################################################################

#比较整数版本的关键词
$appIntVersionKeyword = ("instantclient", "enterprisedb", "graalvm", "nodejs", "microsoft", "openjdk", "tomcat")

#Install为空时则禁用此程序的安装, 最新版本标记为 latest,
$appInstall = @{
    Nginx = ("1.24", "1.25", "latest")
    Mysql = ("5.7", "8.0", "latest")
    Mariadb = ("10.6", "10.11", "11.3", "latest")
    Php = ("5.6", "7.4", "8.1", "8.2", "8.3", "latest")
    PhpBuild = ("7.4", "8.2", "8.3", "latest")
    Pgsql = ("12", "13", "14", "15", "16", "latest")
    Jdk = ("17", "21", "latest")
    Graalvm = ("17", "21", "latest")   #javalvm javaclvm
    MicrosoftJdk = ("11", "17", "21", "latest") #javams javacms
    OracleInstantClient = ("21", "latest")
    Node = ("20", "21", "latest")
    Golang = ("1.21", "1.22", "latest")
    Mongodb = ("4.4", "5.0", "7.0", "latest")
    Tomcat = ("9", "10", "latest")
    Apache = ("2.4", "latest")
    OpenJdk = ("latest") #javaopen javacopen
    Cloudflared = ("latest")
    Redis = ("latest")
    Unbound = ("latest")
    Geoipupdate = ("latest")
    Vim = ("latest")
    Aria2c = ("latest")
    Ffmpeg = ("latest")
    Putty = ("latest")
    #Bun = ("latest")
}

#旧版本程序
$appArchive = @{
    Php = @{
        "5.6" = @{
            Size = 23032238; Version = "5.6.40"; Label = "5.6"; File = "php-5.6.40-nts-Win32-VC11-x64.zip";
            Url = "https://windows.php.net/downloads/releases/archives/php-5.6.40-nts-Win32-VC11-x64.zip"
        }
        "7.4" = @{
            Size = 26131576; Version = "7.4.32"; Label = "7.4"; File = "php-7.4.32-nts-Win32-vc15-x64.zip";
            Url = "https://windows.php.net/downloads/releases/archives/php-7.4.32-nts-Win32-vc15-x64.zip"
        }
    }
    Mysql = @{
        "5.5" = @{
            Size = 207688617; Version = "5.5.62"; Label = "5.5"; File = "mysql-5.5.62-winx64.zip";
            Url = "https://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-5.5.62-winx64.zip"
        }
        "5.6" = @{
            Size = 452005649; Version = "5.6.51"; Label = "5.6"; File = "mysql-5.6.51-winx64.zip";
            Url = "https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.51-winx64.zip"
        }
        "5.7" = @{
            Size = 352891656; Version = "5.7.44"; Label = "5.7"; File = "mysql-5.7.44-winx64.zip";
            Url = "https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.44-winx64.zip"
        }
    }
}

#对应的配置文件名
$appConfFile = @{
    Php = "php.ini"
    Phpbuild = "php.ini"
    Mysql = "my.ini"
    Mariadb = "my.ini"
    Redis = "redis.conf"
    Nginx = "nginx.conf"
    Geoipupdate = "GeoIP.conf"
    Unbound = "service.conf"
}

#info, link=需要快捷方式的exe, thrid, proxy, bin=exe文件在bin目录下, url, search, share=安装在share目录下, intversion=比较整数版本, suffix=多编译版本快捷命令后缀
$appData = @{
    Apache = @{
        Info = "由 apachelounge.com 编译"; Bin = $true; Third = $true; Link = ("ab", "abs"); Update = @{};
        Url = "https://www.apachelounge.com/download/"; Search = "/download/([0-9a-zA-Z\-\.\/]+).zip"
    }
    Nginx = @{
        Info = "由 nginx.org 编译"; Link = ("nginx"); Update = @{};
        Url = "https://nginx.org/en/download.html"; Search = '/download/nginx-([0-9\.]+).zip">nginx/Windows-([0-9\.]+)'
    }
    Mysql = @{
        Info = "由 mysql.com 编译"; Bin = $true; Proxy = $true; Link = ("mysql", "mysqldump", "mysqladmin"); Update = @{};
        Url = "https://dev.mysql.com/downloads/mysql/"; Search = ">([0-9\.]+) "
        #"https://repo.mysql.com/yum/"   #"mysql-([0-9\.]+)-community"
    }
    Mariadb = @{
        Info = "由 mariadb.org 编译"; Bin = $true; Link = ("mariadb", "mariadb-dump", "mariadb-admin", "mariadb-backup");
        Url = "https://mariadb.org/"; Search = "mariadb/([0-9\.]+)"; Update = @{}
    }
    Php = @{
        Info = "由 php.net 编译"; Link = ("php"); Update = @{};
        Url = "https://windows.php.net/downloads/releases/"; Search = "php-([0-9\.]+)-nts-([0-9a-zA-Z\-\.\/]+)-x64.zip"
    }
    PhpBuild = @{
        Info = "由 apachelounge.com ==> Jan-E(phpdev.toolsforresearch.com) 编译"; Link = ("php"); Third = $true; Update = @{};
        Url = "https://phpdev.toolsforresearch.com/"; Search = ""; Suffix="build"
    }
    Pgsql = @{
        Info = "由 enterprisedb.com 编译"; Link = ("psql", "pg_dump"); Bin = $true; Update = @{};
        Url = "https://www.enterprisedb.com/download-postgresql-binaries"; 
        Search = '([0-9\.]+)<a href="https://sbp.enterprisedb.com/getfile.jsp\?fileid=([0-9]+)"'
    }
    Jdk = @{
        Info = "由 oracle.com 编译"; Proxy = $true; Bin = $true; Update = @{}; #Link = ("java", "javac");
        Url = "https://www.oracle.com/java/technologies/downloads/"; 
        Search = "https://download.oracle.com/([0-9a-z\.\-_\/\+]+)/([0-9a-z\.\-_\+]+)windows-x64_bin.zip"
    }
    Graalvm = @{
        Info = "由 oracle.com 编译"; Proxy = $true; Bin = $true; Update = @{}; #Link = ("java", "javac");
        Url = "https://www.oracle.com/java/technologies/downloads/";  Suffix="lvm";
        Search = "https://download.oracle.com/graalvm/([0-9]+)/latest/graalvm-jdk-([0-9\.\+]+)_windows-x64_bin.zip"
    }
    MicrosoftJdk = @{
        Info = "由 microsoft.com 提供"; Bin = $true;  Update = @{}; Suffix = "ms"; #Link = ("java", "javac")
        Url = "https://learn.microsoft.com/en-us/java/openjdk/download"; Search = "microsoft-jdk-([0-9\.]+)-windows-x64.zip"
    }
    OpenJdk = @{
        Info = "由 openjdk.org 提供"; Bin = $true; Update = @{};  Suffix = "open"
        Url = "https://jdk.java.net/"; Search = ">JDK ([0-9]+)<"
    }
    OracleInstantClient = @{
        Info = "由 oracle.com 编译"; Proxy = $true; Share = $true; Update = @{}; 
        Url = "https://www.oracle.com/database/technologies/instant-client/winx64-64-downloads.html"
        Search = "([0-9]+)/instantclient-basic-windows.x64-([0-9a-z\.]+)"
    }
    Node = @{
        Info = "由 nodejs.org 编译"; Update = @{};  #Link = ("node", "npm.cmd", "npx.cmd"); 
        Url = "https://nodejs.org/en/download"; Search = "https://nodejs.org/dist/v([0-9\.]+)/node-v([0-9\.]+)-win-x64.zip"
    }
    Golang = @{
        Info = "由 go.dev 编译"; Bin = $true; Update = @{};
        Url = "https://go.dev/dl"; Search = "/dl/go([0-9\.]+).windows-amd64.zip"
        #Mirror = "https://mirrors.ustc.edu.cn/golang/"
        #MirrorSearch = "go([0-9\.]+).windows-amd64.zip"
    }
    Tomcat = @{
        Info = "由 tomcat.apache.org 编译"; Bin = $true; Update = @{}; 
        Url = "https://tomcat.apache.org"; Search = "Tomcat ([0-9\.]+) Released"
    }
    Mongodb = @{
        Info = "由 mongodb.com 编译"; Bin = $true; Link = ("mongo", "mongos"); Update = @{};
        Url = "https://www.mongodb.com/try/download/community"; Search = "mongodb-windows-x86_64-([0-9\.]+).zip"
    }
    Unbound = @{
        Info = "由 nlnetlabs.nl 编译"; Share = $true; Link = ("unbound", "unbound-cache", "unbound-anchor", "unbound-checkconf");
        Url = "https://nlnetlabs.nl/projects/unbound/download/"; Search = "/downloads/unbound/unbound-([0-9\.]+).zip"; Update = @{}
    }
    Geoipupdate = @{
        Info = "由 maxmind.com 编译"; Share = $true; Link = ("geoipupdate"); Update = @{};
        Url = "https://github.com/maxmind/geoipupdate/"; Search = "/maxmind/geoipupdate/releases/tag/v([0-9\.]+)"
    }
    Cloudflared = @{
        Info = "由 cloudflared.com 编译"; Share = $true; Link = ("cloudflared"); Update = @{};
        Url = "https://github.com/cloudflare/cloudflared/"; Search = "/cloudflare/cloudflared/releases/tag/([0-9\.]+)"
    }
    Vim = @{
        Info = "由 vim.org 编译"; Share = $true; Link = ("vim"); Update = @{};
        Url = "https://github.com/vim/vim-win32-installer/"; Search = "gvim_([0-9\.\-]+)_x64.zip"
    }
    Redis = @{
        Info = "由 github.com (zkteco-home) 编译"; Thrid = $true; Link = ("redis-cli", "redis-benchmark", "redis-check-aof", "redis-check-rdb");
        Url = "https://github.com/zkteco-home/redis-windows/"; Update = @{};
        Search = "/zkteco-home/redis-windows/releases/tag/([0-9\.]+)"
    }
    Aria2c = @{
        Info = "由 github.com (aria2) 编译"; Share = $true; Link = ("aria2c"); Update = @{};
        Url = "https://github.com/aria2/aria2/"; Search = "tag/release-([0-9\.]+)"
    }
    Ffmpeg = @{
        Info = "由 ffmpeg.org (btBN) 编译"; Third = $true; Share = $true; Bin=$true; Link = ("ffmpeg"); Update = @{};
        Url = "https://github.com/FFmpeg/FFmpeg/"; Search = ""
    }
    Putty = @{
        Info = ""; Share = $true; Link = ("putty", "pscp", "psftp", "puttytel", "plink", "pageant", "puttygen", "pterm");
        Url = "https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html"; Update = @{};
        Search = "putty-64bit-([0-9\.]+)-installer.msi"
        #https://the.earth.li/~sgtatham/putty/latest/w64/putty.zip
    }
    Composer = @{
        Info = "由 getcomposer.org 发布"; Share = $true; Update = @{};
        Url = "https://getcomposer.org/download/"; Search = "download/([0-9\.]+)/composer.phar"
    }
    Bun = @{
        Info = ""; Update = @{}; Url = "https://github.com/oven-sh/bun/"
        Search = ""
    }
}

######################################################################################################

#脚本命令参数 -dir -path -github -oracle -php -proxy -static
# -dir -path $dirRoot  设置安装目录, 默认安装在脚本所在文件夹
# -github $isGithub  是否能够访问github.com
# -oracle $isOracle  是否能够访问oracle.com (mysql.com)
# -php defaultPhpVersion  默认php版本
# -proxy $proxyUrl  代理github.com oracle.com mysql.com 等无法正常访问的网址, '?url='=> 网页, '?file=' =>文件, '&save=' => 保存文件名
# -static $staticUrl  固定的编译好的dll和exe
$argsCount = $args.Count
if($argsCount -gt 0){
    $i = 0
    foreach($arg in $args){
        $atr = $arg.ToLower()
        $nextId = $i + 1
        if($nextId -gt $argsCount) { Continue }
        $val = $args[$nextId]
        $argIsUrl = $val -match "^(http|https)://([\w-]+\.)+[\w-]+(/[\w-./?%&=:/]*)?$"
        if(("-dir", "-path").Contains($atr)){
            $dirRoot = $val
        }elseif("-github" -eq $atr){
            $isGithub = $val
        }elseif("-oracle" -eq $atr){
            $isOracle = $val
        }elseif("-php" -eq $atr -and $appInstall.Php.Contains($val)){
            $defaultPhpVersion = $val
        }elseif("-proxy" -eq $atr -and $argIsUrl){
            $proxyUrl = $val
        }elseif("-static" -eq $atr -and $argIsUrl){
            $staticUrl = $val
        }else{
        }
        $i++
    }
}


######################################################################################################
#function
function readFileLines($file){
    #[System.IO.File]::ReadLines($file) | ForEach-Object { $_ }
    return [System.IO.File]::ReadLines($file)
}

function saveByteToFile($byte, $file){
    return [Io.File]::WriteAllBytes($file, $byte)
}

function getMd5($str){
    return ([System.BitConverter]::ToString((New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider).
            ComputeHash((New-Object -TypeName System.Text.UTF8Encoding).GetBytes($str)))).Replace("-","")
}

function ucFirst($str){
    return (Get-Culture).TextInfo.ToTitleCase($str)
}

function getHost($url){
    #([System.Uri]"http://www.domain.com/folder/").Host -replace '^www\.'
    return ([System.Uri]$url).Host -replace '^www\.'
}

function clearWebRequestCache(){
    if(Test-Path $dirCache){
        Remove-Item -Path $dirCache -Recurse -Filter "*.txt"
    }
    New-Item -Path $dirCache -ItemType Directory | Out-Null
}

function isAvailUrl($url){
    return $url -match "^(http|https)://([\w-]+\.)+[\w-]+(/[\w-./?%&=:/]*)?$"
}

function isNewVersion($checkVersion, $oldVersion){
    return [System.Version]$checkVersion -gt [System.Version]$oldVersion
}

function deleteFiles($dir, $filter=$null){
    if(!($filter -is [string])) { $filter = "*" }
    Get-ChildItem -Path (Join-Path $dir "*") -Recurse -Filter $filter | Remove-Item
}

function deleteDevelFiles(){
    foreach($ext in $deleteDevelFileType) {
        deleteFiles $dirRoot ("*." + $ext)
    }
}

function getSize($file){
    if(Test-Path $file) { return (Get-Item $file).Length } else { return 0 }
}

function isOfficial($app){
    return !($appData.ContainsKey($app) -and $appData.$app.ContainsKey("Third") -and $appData.$app.Third)
}

# function isIntVersion($app){
#     return $appData.$app.ContainsKey("IntVersion") -and $appData.$app.IntVersion
# }

function getLinkSuffix($app){
    if($appData.ContainsKey($app) -and $appData.$app.ContainsKey("Suffix") -and $appData.$app.Suffix -is [string]){
        return $appData.$app.Suffix
    }
    return ""
}

#function getMaximumMinimum($arr){
#    return ($arr | Measure-Object -Maximum) #-Minimum
#}

function isMultiple($app){
    return $appInstall.ContainsKey($app) -and $appInstall.$app.Count -gt 1
}

function isBinPath($app){
    return $appData.ContainsKey($app) -and $appData.$app.ContainsKey("Bin") -and $appData.$app.Bin
}

function isProxy($app){
    if($appData.ContainsKey($app)){
        if($appData.$app.ContainsKey("Proxy") -and $appData.$app.Proxy){
            return $true
        }
        $url = $appData.$app.Url
    }elseif(isAvailUrl $app){
        $url = $app
    }else{
        return $false
    }
    if($url.Contains("github.com")){
        return !$isGithub
    }elseif($url.Contains("oracle.com") -or $url.Contains("mysql.com")){
        return !$isOracle
    }else{
        return $false
    }
}

function arrayUnique($arr){
    $result = @()
    foreach($v in $arr){
        $v = $v.ToString().Trim()
        if(!$result.Contains($v)) { $result += $v }
    }
    return $result
}

function getFileFromPath($url){
    $arr = $url.ToString().Replace("\", "/").Split('/')
    return $arr[$arr.Count - 1]
}

function createDirectory($dir){
    if(!(Test-Path -Path $dir)){
        New-Item -Path $dir -ItemType Directory | Out-Null
    }
}

function isPubNet(){
    try{ 
        $ip6 = (Invoke-WebRequest "https://v6.ipip.net" -TimeoutSec 3).Content
    }catch{ $ip6 = "" }
    try{ 
        $ip4 = (Invoke-WebRequest "https://v6.ipip.net" -TimeoutSec 3).Content
    }catch{ $ip4 = "" }
    return $ips.Contains($ip6) -or $ips.Contains($ip4)
}

function getMinVer($app){
    $min = "0.0.1"
    if(isMultiple $app) { $min = ($appInstall.$app | Measure-Object -Minimum).Minimum.ToString() }
    if(!$min.Contains(".")) { $min+= ".0" }
    if("mariadb" -eq $app.ToLower()){
        foreach($v in $appInstall.$app){
            if("latest" -ne $v -and (isNewVersion $min $v)){
                $min = $v
            }
        }
    }
    return $min
}

function getPrevVer($ver, $step = 1){
    $arr = $ver.Split('.')
    $len = $ver.Length
    $last = $arr[$arr.Count - 1]
    $lastLen = $last.Length
    $prev = (($last -as [int]) - $step).ToString()
    $prevLen = $prev.Length
    $remainder = $lastLen - $prevLen
    $fix = ""
    for($i = 0; $i -lt ($remainder); $i++){
        $fix += "0"
    }
    return $ver.Substring(0, $len - $lastLen) + $fix + ([Math]::Abs($prev)).ToString()
}
######################################################################################################

function echoStatus($status, $noNewLine = $false){
    if($status){
        $status = [Char]0x2713
        $color = $colorSuccess
    }else{
        $status = [Char]0x2717
        $color = $colorError
    }
    $obj = @{
        Object = $status
        ForegroundColor = $color
    }
    if($noNewLine) {$obj.NoNewLine = $true }
    Write-Host @obj
}

function echoStr($str, $color = $null, $noNewLine = $false){
    $obj = @{Object = $str}
    if($noNewLine) {$obj.NoNewLine = $true}

    if($color -eq $true){
        $color = $colorOfficial
    }elseif($color -eq $false){
        $color = $colorThird
    }elseif($color -is [string]){
        $color += ""
    }else{
        $color = $null
    }
    if($null -ne $color){ $obj.ForegroundColor = $color }
    Write-Host @obj
}

function echoDownloadProgress($cur, $total, $color = "default", $colorRemainder = "default", $str = ">", $sleep = 1){
    if("default" -eq $color) { $color = $colorProgress }
    if("default" -eq $colorRemainder) { $colorRemainder = $colorProgressRemainder }

    $step = $total / $echoProgressLength
    $curInt = [System.Math]::Floor($cur / $step)
    for($i=0; $i -le $curInt; $i++){
        echoStr $str $color $true
        Start-Sleep -Milliseconds $sleep
    }
    $remainder = $echoProgressLength - $curInt
    for($i=0; $i -le $remainder; $i++){
        echoStr $str $colorRemainder $true
        Start-Sleep -Milliseconds $sleep
    }
    echoStr ($echoTab + [Math]::Floor($curInt / $echoProgressLength * 100) + "/100") $colorNormal
}

function echoWaitProgress($color="default", $str = "#", $len = $null, $sleep = 1){
    if("default" -eq $color){ $color = $colorProgress }
    if(!($len -as [int])) { $len = $echoProgressLength } else { $len = $len -as [int] }
    for($i=0; $i -le $len; $i++){
        if($i -eq $len){
            echoStr $str $color
        }else{
            echoStr $str $color $true
        }
        Start-Sleep -Milliseconds $sleep
    }
}

function echoAppCheck($app, $color = "DarkGreen", $str = "="){
    $official = isOfficial $app
    $s = $app
    if($official -eq $false) { $s += "(编译)" }
    echoWaitProgress $color $str
    echoStr "检测" $null $true
    echostr ($echoTab + $s + $echoTab + $appData.$app.Info) $official
}

function echoWebRequestError($url, $err){
    #echoStr ($str + "访问") $colorWarning $true
    #echoStr ($echoTab + $url.ToString() + $echoTab) $colorNormal $true
    echoStr ($echoTab + "错误: " + $url.ToString()) $colorWarning $true
    echoStr ($echoTab + $err) $colorError
}

function echoCreateLink($app, $cmd, $path, $addi = $null){
    echoStr "创建 " $colorProgress $true
    echoStr $app $colorSuccess $true
    if($addi -and $addi -is [string]) { echoStr (" " + $addi + " ") $colorNormal $true }
    echoStr " 命令" $colorProgress $true
    echoStr ($echoTab + $cmd + $echoTab) $colorOfficial $true
    echoStr $path $colorProgressRemainder
}
######################################################################################################
function deletePgAdmin(){
    if($deletePgAdmin){
        $folder = "pgAdmin 4"
        $path = Join-Path $dirLocal "pgsql"
        foreach($dir in Get-ChildItem $path){
            $dir = Join-Path $path $dir
            $dir = Join-Path $dir $folder
            if(Test-Path $dir){
                echoStr "删除" $null $true
                echoStr ($echoTab + $dir + $echoTab) $colorThird
                Remove-Item -Path $dir -Recurse -Force
            }
        }
    }
}

function initPipMirror(){
    $dir = Join-Path $dirUserHome "pip"
    createDirectory $dir
    $ini = Join-Path $dir "pip.ini"
    if("/" -ne $pipMirrorUrl.Substring($pipMirrorUrl.Length - 1, 1)){
        $pipMirrorUrl += "/"
    }
    $arr = $pipMirrorUrl.Split('/')
    $mirrorHost = $arr[2]
    $content = "[global]`nindex-url = " + $pipMirrorUrl + "`n[install]`ntrusted-host = " + $mirrorHost
    echoStr "设置Pip镜像" $null $true
    echoStr ($echoTab + $ini + $echoTab) $colorNormal $true
    echoStr $pipMirrorUrl $colorSuccess
    Set-Content -Path $ini -Value $content -Force
}

function initVimrc(){
    $ini = Join-Path $dirUserHome "_vimrc"
    echoStr "设置Vim高亮语法" $null $true
    echoStr ($echoTab + $ini + $echoTab) $colorNormal
    $content = "syntax on`nset autoindent`nset cursorline`nset nu`nset ru`nset tabstop=4`nset shiftwidth=4"
    Set-Content -Path $ini -Value $content -Force
}

function initRunAsRoot(){
    $file = Join-Path $dirBin "_root.bat"
    echoStr "创建命令" $null $true
    echoStr ($echoTab + "_root" + $echoTab) $colorNormal $true
    echoStr "管理员权限命令行" $colorThird
    Set-Content -Path $file -Value "@echo off`nPowershell -Command `"Start-Process cmd -Verb RunAs`"" -Force
}

function getVersionFile($app, $result){
    $str = $app.ToLower()
    $suffix = ""
    if("phpbuild" -eq $str){
        $str = "php"
        $suffix = "build"
    }
    if(isMultiple $app){
        $str += $result.Label + $suffix
    }
    return Join-Path $dirVersion ($str + ".txt")
}

function getAppPath($app, $result){
    $str = $app.ToLower()
    if($appData.$app.ContainsKey("Share") -and $appData.$app.Share){
        $dir = $dirShare
    }else{
        $dir = $dirLocal
    }
    $dir = Join-Path $dir $str
    if(isMultiple $app) { $dir = Join-Path $dir $result.Label }
    if("phpbuild" -eq $str) { $dir = $dir.Replace("phpbuild", "php") + "build" }
    createDirectory $dir

    $dbDir = $logDir = $confDir = $backupDir = $tmpDir = $sessDir = $uploadDir = ""
    $progs = ("mysql", "mariadb", "mongodb", "redis", "pgsql", "apache", "nginx", "php", "phpbuild")
    if($progs.Contains($str)){
        if(!("apache", "nginx", "php", "phpbuild").Contains($str)){
            $dbDir = $dir.Replace($dirLocal, $dirDb).Replace($dirShare, $dirDb)
            createDirectory $dbDir
        }
        $logDir = $dir.Replace($dirLocal, $dirLog).Replace($dirShare, $dirLog)
        createDirectory $logDir
        $confDir = $dir.Replace($dirLocal, $dirConfig).Replace($dirShare, $dirConfig)
        createDirectory $confDir
        $backupDir = $dir.Replace($dirLocal, $dirConfigBackup).Replace($dirShare, $dirConfigBackup)
        createDirectory $backupDir
        if(("apache", "nginx").Contains($str)){
            createDirectory (Join-Path $confDir "conf.d")
        }
        $tmpDir = $dir.Replace($dirLocal, $dirTemp).Replace($dirShare, $dirTemp)
        createDirectory $tmpDir
        if(("php", "phpbuild").Contains($str)){
            $sessDir = Join-Path $tmpDir "session"
            createDirectory $sessDir
            $uploadDir = Join-Path $tmpDir "upload"
            createDirectory $uploadDir
        }
    }
    return @{
        InstallDir = $dir
        DbDir = $dbDir
        LogDir = $logDir
        ConfDir = $confDir
        BackupDir = $backupDir
        TempDir = $tmpDir
        SessionDir = $sessDir
        UploadDir = $uploadDir
    }
}

function createCmdLink($app){
    if(!$appData.$app.ContainsKey("Link")) { return }
    $multiple = isMultiple $app
    $binPath = isBinPath $app
    $str = $app.ToLower()
    $suffix = getLinkSuffix $app
    $exeComposer = Join-Path $dirShare ("composer\composer.phar")

    foreach($vl in $appData.$app.Update.Keys){
        $dir = $appData.$app.Update.$vl.InstallDir
        if($binPath) { $dir = Join-Path $dir "bin" }
        $appName = $app + " " + $vl
        foreach($file in $appData.$app.Link){
            if(!$file.Contains(".")){ $file += ".exe" }
            $exeFile = Join-Path $dir $file
            $name = $file.Split('.')[0].ToLower()
            if($multiple){
                $link1 = Join-Path $dirBin ($name + $vl + ".bat")
                $link2 = Join-Path $dirBin ($name + $vl.Replace(".", "") + ".bat")
            }else{
                $link1 = Join-Path $dirBin ($name + ".bat")
                $link2 = ""
            }
            if($suffix -and $suffix.Length -gt 0){
                $link1 = $link1.Replace(".bat", $suffix + ".bat")
                $link2 = $link2.Replace(".bat", $suffix + ".bat")
            }

            $content = "@echo off`n`"" + $exeFile + "`" %*"
            if($multiple){
                echoCreateLink $appName ($name + $vl) $exeFile
            }else{
                echoCreateLink $appName $name $exeFile
            }
            Set-Content -Path $link1 -Value $content -Force
            if($link2 -and $link2 -ne $link1){
                echoCreateLink $appName ($name + $vl.Replace(".", "")) $exeFile
                Copy-Item -Path $link1 -Destination $link2 -Force
            }

            if(("php", "phpbuild").Contains($str)){
                $composer = "composer"
                $link3 = $link1.Replace("php", $composer)
                $cmdName = (getFileFromPath $link3).Replace(".bat", "")
                echoCreateLink $appName $cmdName $exeFile "Composer"
                Set-Content -Path $link3 -Value $content.Replace("%*", ("`"" + $exeComposer + "`" %*")) -Force
                if($link2 -and $link2 -ne $link1){
                    $link4 = $link2.Replace("php", $composer)
                    $cmdName = (getFileFromPath $link4).Replace(".bat", "")
                    echoCreateLink $appName $cmdName $exeFile "Composer"
                    Copy-Item -Path $link3 -Destination $link4 -Force
                }
            }
        }
    }
}

######################################################################################################
function getWebRequest($url, $headers = $false){
    try{
        if($headers){
            $result = Invoke-WebRequest -Uri $url -UserAgent $userAgent -Method Head
        }else{
            $result = Invoke-WebRequest -Uri $url -UserAgent $userAgent
        }
    }catch{ $result = $_.ToString() }
    return $result
}

function isRemoteExists($url){
    $request = getWebRequest $url $true
    if($request -is [string]){
        return $false
    }
    return 200 -eq $request.StatusCode
}

function getRemoteSize($url){
    #echoStr "获取" $null $true
    #echoStr ($echoTab + $url.ToString() + $echoTab) $colorNormal $true
    #echoStr "的文件大小."
    $result = getWebRequest $url $true
    if($result -is [string]){
        echoWebRequestError $url $result
        return -1
    }
    $key = "Content-Length"
    if($result.Headers.ContainsKey($key)){
        if($result.Headers.$key -is [array]){
            return [Int64]($result.Headers.$key[0])
        }else{
            return [Int64]$result.Headers.$key
        }
    }
    return -1
}

function isDownloaded($url, $file){
    $size = getSize $file
    if($size -and $size -gt 0){
        if(isAvailUrl $url){
            return $size -eq (getRemoteSize $url)
        }elseif(![regex]::Match($url.ToString(), "([^0-9]+)").Success){
            return $size -eq [Int64]$url
        }else{
            return $false
        }
    }
    return $false
}

function parseContent($content){
    $content = [string]::join("",($content.Split("`n")))
    $content = $content -replace "\s{2,}", ""
    $content = $content.Replace("</span></p><p>", "")
    $content = $content.Replace('</span><div><div class="m-5">', '')
    return $content
}

function getWebContent($url, $useCache = $true){
    echoStr "访问" $null $true
    echoStr ($echoTab + $url + $echoTab) $colorNormal $true
    $cacheFile = "_" + $today + "-" + (getHost $url) + "-" + (getMd5 $url) + ".txt"
    $cachePath = Join-Path $dirCache $cacheFile
    if($useCache){
        if(Test-Path $cachePath){
            echoStatus $true
            #echoStr "从缓存文件: " $null $true
            #echoStr $cacheFile $colorNormal $true
            #echoStr " 中读取数据."
            return parseContent (Get-Content $cachePath)
        }
    }
    $content = ""
    $result = getWebRequest $url
    if($result -is [string]){
        echoStatus $false
        echoWebRequestError $url $result
    }else {
        echoStatus $true
        $content = $result.Content
        if($useCache -and $content.Length -gt 0){
            Set-Content -Path $cachePath -Value $content
        }
    }
    return parseContent $content
}

function getMatchResult($app, $search = $null){
    if(isAvailUrl $app){
        $url = $app
    }else{
        $url = $appData.$app.Url
    }
    if(isProxy $app -and !$url.Contains($proxyUrl)){
        $url = $proxyUrl + "?url=" + [uri]::EscapeDataString($url)
    }
    $content = getWebContent $url
    if(!($search -is [string] -and $search.Length -gt 0)) {
        if($appData.ContainsKey($app) -and $appData.$app.ContainsKey("Search")){
            $search = $appData.$app.Search
        }else{
            $search = "没有定义正则匹配规则"
        }
    }
    if(isMultiple $app){
        $result = [regex]::Matches($content, $search)
        return arrayUnique $result
    }else{
        return ([regex]::Match($content, $search)).ToString()
    }
}

function parseUrl($url, $pos = 1, $removeStr = $null, $split = "-"){
    $url = $url.ToString()
    $file = getFileFromPath $url
    $arr = $file.Split($split)
    $ver = $arr[$pos]
    if($null -ne $removeStr){
        if($removeStr -is [string]){
            $ver = $ver.Replace($removeStr, "")
        }elseif($removeStr -is [array]){
            foreach($rs in $removeStr){
                $ver = $ver.Replace($rs, "")
            }
        }else{
            $ver += ""
        }
    }

    if($ver.Contains(".")){
        $arr = $ver.Split('.')
        $label = $arr[0] + "." + $arr[1]
        $status = $false
        foreach($s in $appIntVersionKeyword){
            $s = $s.ToString()
            if($url.Contains($s)){
                $status = $true
                Break
            }
        }
        if($status){
            $label = $arr[0]
        }
    }else{
        $label = $ver
    }
    return @{
        Url = $url
        Version = $ver
        File = $file
        Label = $label
    }
}

function getWebFile($url, $save = $null, $size = $null){
    $url = $url.ToString()
    if($null -eq $size) { $size = getRemoteSize $url }
    if(!($save -and $save -is [string])){
        $save = getFileFromPath $url
        $save = Join-Path $dirDownload $save
    }
    if(isDownloaded $size $save){
        echoStr "文件已存在:" $null $true
        echoStr ($echoTab + $save + $echoTab) $colorNormal
        return $true
    }

    echoStr "开始下载:" $null $true
    echoStr ($echoTab + $url + $echoTab) $colorNormal $true
    $err = ""
    try{
        Invoke-WebRequest -Uri $url -UserAgent $userAgent -OutFile $save
    }catch{
        $err = $_.ToString()
    }
    if($err){ echoWebRequestError $url $err }
    $status = isDownloaded $size $save
    if(!$status -and (Test-Path -Path $save)) { Remove-Item -Path $save }
    return $status
}

function downloadAsync($app, $result){
    if(!$result) { return }
    $save = Join-Path $dirDownload $result.File
    if($result.Url.Contains("toolsforresearch")){
        $save = $save.Substring(0, $save.Length - 3) + "build.zip"
    }
    $result.SaveFile = $save
    $path = getAppPath $app $result
    $result.InstallDir = $path.InstallDir
    $result.DbDir = $path.DbDir
    $result.LogDir = $path.LogDir
    $result.ConfDir = $path.ConfDir
    $result.BackupDir = $path.BackupDir
    $result.TempDir = $path.TempDir
    $result.SessionDir = $path.SessionDir
    $result.UploadDir = $path.UploadDir
    # foreach($key in ("InstallDir", "DbDir", "LogDir", "ConfDir", "BackupDir", "TempDir", "SessionDir")){
    #     if(!$result.ContainsKey($key)){
    #         $result.$key = $path.$key
    #     }
    # }
    
    if($result.ContainsKey("Size") -and $result.Size -gt 0){
        $remoteSize = $result.Size
    }else{
        $remoteSize = getRemoteSize $result.Url
        if($remoteSize -le 0){
            for($i=0; $i -lt 5; $i++){
                $remoteSize = getRemoteSize $result.Url
                if($remoteSize -le 0){
                    echoStr ("第 " + ($i+1) + " 次 ") $colorThird $true
                    echoStr "尝试获取" $null $true
                    echoStr ($echoTab + $url + $echoTab) $colorNormal $true
                    echoStr ("大小" + $echoTab) $null
                }else{
                    Continue
                }
            }
            if($remoteSize -le 0) {
                echoStr ("当前网络无法获取 " + $result.Url + " 大小") $colorThird
                echoStr ("程序将退出执行, 请更换网络后重试.") $colorThird
                Break script
            }
        }
        $result.Size = $remoteSize
    }

    $vl = $result.Label
    $appData.$app.Update.$vl = $result
    if(isDownloaded $remoteSize $save){
        echoStr "文件已存在" $null $true
        echoStr ($echoTab + $save + $echoTab) $colorNormal $true
        echoStatus $true
    }else{
        $psl = [powershell]::Create()
        $job = $psl.AddCommand("Invoke-WebRequest").AddParameter("Uri", $result.Url).AddParameter("Outfile", $save).AddParameter("UserAgent", $userAgent)
        #$tid = "Tid-" + $job.InstanceId
        $tid = $app.ToString() + $result.Label
        $downloadAsync.$tid = New-Object psobject -Property @{
            App = $app 
            Label = $result.Label
            Version = $result.Version
            Job = $job
            Psh = $psl
            Url = $result.Url
            File = $result.File
            SaveFile = $save
            Size = $remoteSize
            Official = (isOfficial $app)
            Result = $job.BeginInvoke()
        }
        echoStr "后台下载" $colorProgress $true
        echoStr ($echoTab + ($app + " " + $result.Version + " " + $result.Url) + $echoTab) $colorProgress
    }
}

function isAppNeedCheck($app, $result){
    if(!$appInstall.ContainsKey($app)) { return $false }
    if($appInstall.$app.Count -eq 1) { return $true }

    $vls = $result.Version
    $vls = $vls.Split('-')[0]
    if(!$vls.Contains(".")) { $vls += ".0" }

    $min = getMinVer $app
    if(isNewVersion $min $vls) { return $false }

    $arr = @("0.0.1")
    foreach($v in $appInstall.$app){
        if("latest" -ne $v) { $arr += $v }
    }
    $max = ($arr | Measure-Object -Maximum).Maximum.ToString()
    $max = $max.Split('-')[0]
    if(!$max.Contains(".")) { $max += ".0" }

    if(isNewVersion $vls $max){
        return $appInstall.$app.Contains("latest")
    }else{
        return $appInstall.$app.Contains($result.Label)
    }
}

function checkAppUpdate($app, $result, $unzipFolder = ""){
    if(!$result) { return }
    $vl = $result.Label
    if(!(isAppNeedCheck $app $result) -or $appData.$app.Update.ContainsKey($vl)) { return }

    $path = getAppPath $app $result
    $dir = $path.InstallDir
    $versionFile = getVersionFile $app $result
    $update = $true
    if(Test-Path $versionFile){
        $update = isNewVersion $result.Version (Get-Content $versionFile)
    }

    if($update){
        $official = isOfficial $app
        echoStr "发现" $null $true
        echoStr ($echoTab + $app + $echoTab) $official $true
        echoStr "新版本" $null $true
        echoStr ($echoTab + $result.Version + $echoTab) $official $true
        echoStatus $true $false
        $result.VersionFile = $versionFile
        $result.InstallDir = $dir
        $result.DbDir = $path.DbDir
        $result.LogDir = $path.LogDir
        $result.ConfDir = $path.ConfDir
        $result.BackupDir = $path.BackupDir
        $result.TempDir = $path.TempDir
        $result.SessionDir = $path.SessionDir
        $result.UploadDir = $path.UploadDir
        if($unzipFolder.Contains("%v")){
            $unzipFolder = $unzipFolder.Replace("%v", $result.Version)
        }
        if($unzipFolder.Contains("%s")){
            $unzipFolder = $unzipFolder.Replace("%s", $result.Label)
        }
        $unzipFolder = Join-Path $dirUnzip $unzipFolder
        $result.Unzip = Join-Path $unzipFolder "*"
        downloadAsync $app $result
    }
}
######################################################################################################
function getStaticSendmail(){
    $dir = Join-Path $dirShare "sendmail"
    createDirectory $dir
    $exe = Join-Path $dir "sendmail.exe"
    if(!(Test-Path $exe)){
        # $url = $giteeUrl + "sendmail/"
        # foreach($file in ("libeay32.dll", "sendmail.exe", "sendmail.ini", "ssleay32.dll")){
        #     getWebFile ($url + $file) (Join-Path $dir $file)
        # }
        $url = $staticUrl + "sendmail/Sendmail_v33_TLS1_2.zip"
        $save = Join-Path $dir "sendmail-v33.zip"
        Invoke-WebRequest -Uri $url -UserAgent $userAgent -OutFile $save
        Expand-Archive -Path $save -DestinationPath $dir -Force
    }
}

function getStatic7zip(){
    $dir = Join-Path $dirShare "7z"
    createDirectory $dir
    $exe = Join-Path $dir "7z.exe"
    if(!(Test-Path $exe)){
        Invoke-WebRequest -Uri ($staticUrl + "7z/7z.exe") -UserAgent $userAgent -OutFile (Join-Path $dir "7z.exe")
        Invoke-WebRequest -Uri ($staticUrl + "7z/7z.dll") -UserAgent $userAgent -OutFile (Join-Path $dir "7z.dll")
    }
}

function getLatestCacertificate(){
    $content = getWebContent "https://curl.se/docs/caextract.html"
    $file = [regex]::Match($content, "cacert-([0-9\-]+).pem")
    $file = $file.ToString()
    $ver = $file.Replace("cacert-", "").Replace(".pem", "").Replace("-", ".")
    $update = $true
    $versionFile = Join-Path $dirVersion "cacert.txt"
    if(Test-Path $versionFile){
        $update = isNewVersion $ver (Get-Content $versionFile)
    }
    if($update){
        echoStr ("发现 cacert.pem 新版本") $colorSuccess $true
        echoStr ($echoTab + $ver + $echoTab) $colorSuccess $true
        echoStatus $true $false
        Invoke-WebRequest -Uri "https://curl.se/ca/cacert.pem" -OutFile (Join-Path $dirConfig ("ssl\" + $curlCaFile))
        Set-Content -Path $versionFile -Value $ver -Force
    }
}

function getLatestApache(){
    $app = "Apache"
    $match = getMatchResult $app
    $result = (parseUrl ($appData.$app.Url + ($match[0].ToString().Replace("/download/", ""))))
    $unzip = $app + $result.Label.Replace(".", "")
    checkAppUpdate $app $result $unzip
}

function getLatestPhp(){
    $app = "Php"
    foreach($vl in $appArchive.$app.Keys){
        if($appInstall.$app.Contains($vl)) { checkAppUpdate $app $appArchive.$app.$vl }
    }

    $match = getMatchResult $app
    foreach($file in $match){
        $file = $file.ToString()
        $result = parseUrl ($appData.$app.Url + $file)
        checkAppUpdate $app $result
        if($appInstall.PhpBuild.Contains($result.Label)){
            $url = $result.Url
            $url = $url.Replace("windows.php.net/downloads/releases", "phpdev.toolsforresearch.com")
            $ver = $result.Version
            if(!(isRemoteExists $url)){
                $ver = getPrevVer $ver
                $url = $url.Replace($result.Version, $ver)
                $file = $file.Replace($result.Version, $ver)
            }
            checkAppUpdate "PhpBuild" @{
                Url = $url
                File = $file
                Version = $ver
                Label = $result.Label
            }
        }
    }
}

function getLatestMysql(){
    $app = "Mysql"
    foreach($vl in $appArchive.$app.Keys){
        $result = $appArchive.$app.$vl
        if($appInstall.$app.Contains($vl)){
            $url = $proxyUrl + "?file="+ $result.Url
            $url = getWebContent $url 
            checkAppUpdate $app (parseUrl $url) ("mysql-"+$result.Version+"-winx64")
        }
    }

    $match = getMatchResult $app
    foreach($v in $match){
        $v = $v.ToString().Replace(">", "").Trim()
        $arr = $v.Split('.')
        $vl = $arr[0] + "." + $arr[1]
        $fs = "mysql-" + $v + "-winx64"
        $file = $fs + ".zip"
        $url = "https://dev.mysql.com/get/Downloads/MySQL-" + $vl + "/" +$file
        $url = $proxyUrl + "?file="+$url
        $url = getWebContent $url
        checkAppUpdate $app (parseUrl $url) $fs
    }
}

#https://dev.mysql.com/get/Downloads/MySQL-Cluster-8.0/mysql-cluster-8.0.36-winx64.zip
# function getLatestMysql2(){
#     $app = "Mysql"
#     $match = getContent $app
#     foreach($vls in $match){
#         $arr = $vls.ToString().Split('-')
#         $verLabel = $arr[1]
#         if($appInstall.$app.Contains($verLabel)){
#             $vl = [int]($verLabel.Substring(0,1))
#             if($vl -ge 8) { $el = "9" } else { $el = "7" }
#             $url = "https://repo.mysql.com/yum/mysql-" + $verLabel + "-community/el/" + $el + "/SRPMS/"
#             $content = getWebContent $url
#             $match2 = [regex]::Matches($content, "community-([0-9\.]+)")
#             $ver = $appData.$app.MinVersion
#             foreach($v in $match2){
#                 $v = $v.ToString().Replace("community-", "")
#                 if(isNewVersion $v $ver){
#                     $ver = $v
#                 }
#             }
#             #$url = "https://dev.mysql.com/get/Downloads/MySQL-" + $verLabel + "/mysql-" + $ver + "-winx64.zip"
#             $url = "https://ip.lixsu.top/down/?file=mysql-" + $ver + "-winx64.zip"
#             $url = getWebContent $url
#             checkAppUpdate $app (parseUrl $url)
#         }
#     }
# }

function getLatestMariadb(){
    #https://mirrors.tuna.tsinghua.edu.cn/mariadb/mariadb-11.4.1/winx64-packages/mariadb-11.4.1-winx64.zip
    #https://mirrors.neusoft.edu.cn/mariadb/mariadb-11.3.2/winx64-packages/mariadb-11.3.2-winx64.zip
    #https://mirrors.aliyun.com/mariadb/mariadb-11.3.2/winx64-packages/mariadb-11.3.2-winx64.zip
    #https://archive.mariadb.org/mariadb-11.3.2/winx64-packages/mariadb-11.3.2-winx64.zip
    $app = "Mariadb"
    $match = getMatchResult $app
    foreach($s in $match){
        $ver= $s.ToString().Replace("mariadb/", "")
        $vs = "mariadb-" + $ver
        $file = $vs + "-winx64.zip"
        $url = $vs + "/winx64-packages/" + $file
        $url  = "https://mirrors.aliyun.com/mariadb/" + $url
        #$url = $appData.$app.Url + $url
        checkAppUpdate $app (parseUrl $url) ("mariadb-"+ $ver +"-winx64")
    }
}

function getLatestPgsql(){
    $app = "Pgsql"
    $match = getMatchResult $app
    foreach($file in $match){
        $file = $file.ToString().Replace('"', "").Replace("<a href=", "|")
        $arr = $file.Split('|')
        $ver = $arr[0]
        $downUrl = $arr[1]
        $verLabel = $ver.Split('.')[0]
        checkAppUpdate $app @{
            Url = $downUrl
            Version = $ver
            File = "postgresql-" + $ver + "-windows-x64.zip"
            Label = $verLabel
        } "pgsql"
    }
}

function getLatestNginx(){
    $app = "Nginx"
    $match = getMatchResult $app
    $min = getMinVer $app
    foreach($file in $match){
        $file = $file.ToString().Split('"')[0]
        $arr = $file.Split('-')

        $ver = $arr[1].Replace(".zip", "")
        $file = "nginx-" + $ver + ".zip"
        $verLabel = $ver.Split('.')
        $verLabel = $verLabel[0] + "." + $verLabel[1]
        if(isNewVersion $min $ver) { Continue }

        $url = "https://nginx.org/download/" + $file
        checkAppUpdate $app @{
            Url = $url
            File = $file
            Version = $ver
            Label = $verLabel
        } ("nginx-" + $ver)
    }
}

function getLatestNode(){
    $app = "Node"
    $urlHome = "https://nodejs.org/dist/"
    $content = getWebContent $urlHome
    $match = [regex]::Matches($content, ">v([0-9\.]+)/<")
    $min = getMinVer $app
    foreach($v in $match){
        $v = $v.ToString()
        $v = $v.Replace(">", "").Replace("/<", "").Replace("v", "")
        if(isNewVersion $min $v) { Continue }
        $url = "https://nodejs.org/dist/v"+$v+"/node-v"+$v+"-win-x64.zip"
        $result = parseUrl $url 1 "v"
        checkAppUpdate $app $result ("node-v"+ $result.Version +"-win-x64")
    }
}

function getJdkVersion($verLabel){
    #https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html
    #https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.zip
    #https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_windows-x64_bin.zip
    #https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip
    $url = "https://www.oracle.com/java/technologies/javase/jdk"+$verLabel+"-archive-downloads.html"
    if(isProxy "Jdk"){
        $url = $proxyUrl + "?url=" + $url
    }
    $content = getWebContent $url
    $match = [regex]::Match($content, "jdk-([0-9\.]+)_windows-x64_bin.zip")
    return $match.ToString().Split('-')[1].Replace("_windows", "")
}

function getLatestJdk(){
    $app = "Jdk"
    $match = getMatchResult $app
    foreach($url in $match){
        $url = $url.ToString()
        $arr = $url.Split('/')
        $file = $arr[$arr.Count - 1]
        $arr = $file.Split('-')
        if($file.Contains("graalvm")){
            $vl = $arr[2]
            $app = "Graalvm"
        }else{
            $vl = $arr[1]
            $app = "Jdk"
        }
        $vl = $vl.ToString().Replace("_windows", "")
        $version = getJdkVersion $vl
        checkAppUpdate $app @{
            Url = $url
            Label = $vl
            Version = $version
            File = $file
        }
    }
}

function getLatestOracleInstantClient(){
    #https://download.oracle.com/otn_software/nt/instantclient/1922000/instantclient-basic-windows.x64-19.22.0.0.0dbru.zip
    $app = "OracleInstantClient"
    $match = getMatchResult $app
    $baseUrl = "https://download.oracle.com/otn_software/nt/instantclient/"
    foreach($vs in $match){
        $vs = $vs.ToString()
        $url = $baseUrl + $vs
        $file = $vs.Split('/')[1]
        $arr = $file.Split('-')
        $ver = $arr[3]
        $arr = $ver.Split('.')
        $ver = $arr[0] + "." + $arr[1] + "." + $arr[2] + "." + $arr[3]
        $label = $arr[0] + "." + $arr[1]
        checkAppUpdate $app @{
            Url = $url
            File = $file
            Version = $ver
            Label = $label
        } ("instantclient_" + $arr[0] + "_" + $arr[1])
    }
}

function getLatestMicrosoftJdk(){
    $app = "MicrosoftJdk"
    $match = getMatchResult $app
    foreach($file in $match){
        $result = parseUrl ("https://aka.ms/download-jdk/" + $file.ToString()) 2
        if(!$result.Version.Contains(".")) { Continue }
        checkAppUpdate $app $result
    }
}

function getLatestOpenjdk(){
    $app = "Openjdk"
    $content = getWebContent $appData.$app.Url 
    $match = [regex]::match($content, ">JDK ([0-9]+)<")
    $match = $match.ToString().Replace(">JDK ", "").Replace("<", "")
    $url = $appData.$app.Url + $match + "/"
    $content = getWebContent $url 
    #https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip
    $match = [regex]::match($content, "https://download.java.net/java/GA/jdk([0-9\.]+)/([0-9a-f]+)/([0-9]+)/GPL/openjdk-([0-9\.]+)_windows-x64_bin.zip")
    $match = $match.ToString()
    checkAppUpdate $app (parseUrl $match 1 "_windows")
}

function getLatestGolang(){
    $app = "Golang"
    $match = getMatchResult $app
    $min = "0.0.1"
    if(isMultiple $app) { $min = ($appInstall.$app | Measure-Object -Minimum).Minimum.ToString() }
    foreach($file in $match){
        $file = $file.ToString()
        $ver = $file.Replace("/dl/go", "").Replace(".windows-amd64.zip", "")
        if(isNewVersion $min $ver) { Continue }
        $url = "https://go.dev" + $file
        checkAppUpdate $app (parseUrl $url 0 (".windows", "go")) "go"
    }
}

function getLatestMongodb(){
    $app = "Mongodb"
    $match = getMatchResult $app
    foreach($file in $match){
        #https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-5.0.24.zip
        #$url = $proxyFileUrl + "mongodb/windows/" + $file
        $url = "https://fastdl.mongodb.org/windows/" + $file
        $result = parseUrl $url 3 ".zip"
        checkAppUpdate $app $result ("mongodb-win32-x86_64-windows-"+$result.Version) 
    }
}

function getLatestTomcat(){
    $app = "Tomcat"
    $match = getMatchResult $app
    foreach($v in $match){
        $v = $v.ToString().Replace("Tomcat", "").Replace("Released", "").Trim()
        $vl = $v.Split('.')[0]
        $url = "https://dlcdn.apache.org/tomcat/tomcat-" + $vl + "/v" + $v + "/bin/apache-tomcat-" + $v + "-windows-x64.zip"
        checkAppUpdate $app (parseUrl $url 2) ("apache-tomcat-" + $v)
    } 
}

function getLatestUnbound(){
    $app = "Unbound"
    $match = getMatchResult $app
    $url = "https://nlnetlabs.nl" + $match.Tostring()
    checkAppUpdate $app (parseUrl $url 1 ".zip")
}


function getGithubVersion($app, $repos = $false, $removeStr = $null){
    if(isAvailUrl $app){
        $url = $app
    }else{
        if(!$appData.ContainsKey($app)){ return "0.0.0.1" }
        if($repos){
            $url = $appData.$app.Url.Replace("github.com", "api.github.com/repos") + "releases/latest"
        }else{
            $url = $appData.$app.Url + "tags"
        }
    }
    if(!$isGithub) { $url = $proxyUrl + "?url=" + $url }
    $content = getWebContent $url
    if($repos){
        $match = [regex]::Match($content, "`"name`": `"([0-9a-z\.\-]+)`"")
    }else{
        $match = [regex]::Match($content, "tags/([0-9a-z\.\-]+).zip")
    }
    
    $match = $match.ToString()
    $tms = @("v", "release-" ,"tags/", "tag/", ".zip", "tree", '"name": ', '"', "n", "-de")
    if($removeStr -is [string]){
        $removeStr = $removeStr.Split(',')
        foreach($s in $removeStr){
            if(!$tms.Contains($s)) { $tms += $s }
        }
    }
    foreach($s in $tms){
        $match = $match.Replace($s, "")
    }
    return $match
}

function getLatestRedis(){
    $app = "Redis"
    $ver = getGithubVersion $app
    $url = $appData.$app.Url + "archive/refs/tags/" + $ver + ".zip"
    $url = $proxyUrl + "?file=" + $url + "&save=redis-" + $ver + ".zip"
    $url = getWebContent $url
    checkAppUpdate $app (parseUrl $url 1 ".zip") ("redis-windows-"+$ver)
}

function getLatestCloudflared(){
    $app = "Cloudflared"
    $ver = getGithubVersion $app
    $url = $appData.$app.Url + "releases/download/" + $ver + "/cloudflared-windows-amd64.exe"
    $url = $proxyUrl + "?file=" + $url + "&save=cloudflared-"+$ver+".zip"
    $url = getWebContent $url
    checkAppUpdate $app (parseUrl $url 1 ".zip")
}

function getLatestVim(){
    $app = "Vim"
    $ver = getGithubVersion $app $true
    #https://github.com/vim/vim-win32-installer/releases/download/v9.1.0139/gvim_9.1.0139_x64.zip
    $url = $appData.$app.Url + "releases/download/v" + $ver + "/gvim_" + $ver + "_x64.zip"
    $url = $proxyUrl + "?file=" + $url + "&save=vim-"+$ver + ".zip"
    $url = getWebContent $url
    $result = parseUrl $url 1 ".zip"
    checkAppUpdate $app $result ("vim\vim"+ $result.Label.Replace(".", ""))
}

function getLatestGeoipupdate(){
    $app = "Geoipupdate"
    $ver = getGithubVersion $app
    $url = $appData.$app.Url + "releases/download/v" + $ver + "/geoipupdate_" + $ver + "_windows_amd64.zip"
    $url = $proxyUrl + "?file=" + $url + "&save=geoipupdate-" + $ver + ".zip"
    $url = getWebContent $url
    checkAppUpdate $app (parseUrl $url 1 ".zip") ("geoipupdate_" + $ver + "_windows_amd64")
}

function getLatestAria2c(){
    $app = "Aria2c"
    $ver = getGithubVersion $app
    #https://github.com/aria2/aria2/releases/download/release-1.37.0/aria2-1.37.0-win-64bit-build1.zip
    $url = $appData.$app.Url + "releases/download/release-" + $ver + "/aria2-" + $ver + "-win-64bit-build1.zip"
    $url = $proxyUrl + "?file=" + $url
    $url = getWebContent $url
    checkAppUpdate $app (parseUrl $url) ("aria2-" + $ver + "-win-64bit-build1")
}

function getLatestFfmpeg(){
    #https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl-shared.zip
    $app = "Ffmpeg"
    echoAppCheck $app
    $ver = getGithubVersion $app
    $dir = Join-Path $dirRoot ("share\ffmpeg")
    createDirectory $dir
    $curVersionFile = Join-Path $dirVersion "ffmpeg.txt"
    $update = $true
    if(Test-Path $curVersionFile){
        $update = isNewVersion $ver (Get-Content $curVersionFile)
    }

    if($update){
        Remove-Item -Path $dir -Recurse -Force
        echoStr ("发现 " +$app + " 新版本") $colorSuccess $true
        echoStr ($echoTab + $ver + $echoTab) $colorSuccess $true
        echoStatus $true $false
        $url = "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl-shared.zip"
        $url = $proxyUrl + "?file="+$url
        $url = getWebContent $url
        $size = getRemoteSize $url
        $save = Join-Path $dirDownload "ffmpeg-master-latest-win64-gpl-shared.zip"
        $status = getWebFile $url $save $size
        if($status){
            echoStr ("更新" + $echoTab + $app) $colorSuccess $true
            echoStr ($echoTab + $ver + $echoTab) $colorSuccess 
            Expand-Archive -Path $save -DestinationPath $dirUnzip
            Move-Item -Path (Join-Path $dirUnzip "ffmpeg-master-latest-win64-gpl-shared") -Destination $dir -Force
            Remove-Item -Path (Join-Path $dirUnzip "*") -Recurse -Force
        }
        Set-Content -Path $curVersionFile -Value $ver
        #echoWaitProgress $colorWarning "*"
    }
}

function getLatestPutty(){
    $app = "Putty"
    echoAppCheck $app
    $match = getMatchResult $app
    $ver = $match.Split('-')[2]
    $update = $true
    $curVersionFile = Join-Path $dirVersion "putty.txt"
    if(Test-Path $curVersionFile){
        $update = isNewVersion $ver (Get-Content $curVersionFile)
    }
    $dir = Join-Path $dirShare "putty"
    createDirectory $dir

    if($update){
        $appData.$app.Update.$ver = @{
            InstallDir = $dir
            Version = $ver
            Label = $ver
        }
        echoStr ("发现 " +$app + " 新版本") $colorSuccess $true
        echoStr ($echoTab + $ver + $echoTab) $colorSuccess $true
        echoStatus $true $false
        foreach($file in $appData.$app.Link){
            if(!$file.Contains(".")) { $file += ".exe" }
            $save = Join-Path $dir $file
            $url = "https://the.earth.li/~sgtatham/putty/latest/w64/" + $file
            echoStr ("更新") $null $true
            echoStr ($echoTab + $save + $echoTab) $colorOfficial $true
            echoStr $ver $colorWarning
            Invoke-WebRequest -Uri $url -UserAgent $userAgent -OutFile $save
        }
        Set-Content -Path $curVersionFile -Value $ver
        createCmdLink $app
        #echoWaitProgress $colorWarning "*"
    }
}

function getLatestComposer(){
    $app = "Composer"
    echoAppCheck $app
    $match = getMatchResult $app
    $url = $appData.$app.Url + $match.Replace("download/", "")

    $arr = $match.Split('/')
    $ver = $arr[1]
    $arr = $ver.Split('.')

    $dir = Join-Path $dirRoot ("share\composer")
    createDirectory $dir
    $curVersionFile = Join-Path $dirVersion "composer.txt"
    $update = $true
    if(Test-Path $curVersionFile){
        $update = isNewVersion $ver (Get-Content $curVersionFile)
    }
    if($update){
        $size = getRemoteSize $url
        $save = Join-Path $dirDownload ("composer-" + $ver + ".phar")
        $status = getWebFile $url $save $size

        echoStr ("更新" + $echoTab +$app) $colorSuccess $true
        echoStr ($echoTab + $ver + $echoTab) $colorSuccess 
        if($status){
            Copy-Item -Path $save -Destination (Join-Path $dir "composer.phar") -Force
            Set-Content -Path $curVersionFile -Value $ver
            ##echoWaitProgress $colorWarning "*"
        }
    }
}

function getLatestModFcgid(){
    $url = $appData.Apache.Url
    $content = getWebContent $url
    $match = [regex]::Match($content, "VS(\d+)/modules/mod_fcgid-([\d\.]+)-win64-VS(\d+).zip")
    $url += $match.ToString()
    $result = parseUrl $url
    $dir = Join-Path $dirLocal "apache"
    $curVersionFile = Join-Path $dirVersion "mod_fcgid.txt"
    $update = $true
    if(Test-Path $curVersionFile){
        $update = isNewVersion $result.Version (Get-Content $curVersionFile)
    }
    if($update){
        $size = getRemoteSize $url
        $save = Join-Path $dirDownload $result.File
        $status = getWebFile $url $save $size
        $arr = Get-ChildItem -Path $dir
        $dl = @()
        foreach($d in $arr){
            $d = Join-Path $dir $d
            if(Test-Path (Join-Path $d "bin\httpd.exe")){
                $dl += (Join-Path $d "modules")
            }
        }

        if($status){
            echoStr ("更新" + $echoTab + "mod_fcgid") $colorSuccess $true
            echoStr ($echoTab + $result.Version + $echoTab) $colorSuccess 
            Expand-Archive -Path $save -DestinationPath $dirUnzip
            foreach($d in $dl){
                Copy-Item -Path (Join-Path $dirUnzip "mod_fcgid.so") -Destination (Join-Path $d "mod_fcgid.so") -Force
            }
            Remove-Item -Path (Join-Path $dirUnzip "*") -Recurse -Force
            Set-Content -Path $curVersionFile -Value $result.Version
            #echoWaitProgress $colorWarning "*"
        }
    }
}

function getExtensionRedis(){
    #https://github.com/dk-sirk/phpredis-windows-release/tags
    #https://github.com/dk-sirk/phpredis-windows-release/releases/download/php_redis-6.0.2-php-8.3/php_redis-6.0.2-php-8.3.7z
    $homeUrl = "https://github.com/dk-sirk/phpredis-windows-release/"
    $url = $homeUrl + "tags"
    if(!$isGithub) { $url = $proxyUrl + "?url=" + $url }
    $content = getWebContent $url
    $match = [regex]::Match($content, "tags/([0-9a-z\.\-_]+).zip")
    $match = $match[0].ToString().Split('-')
    $ver = $match[1]
    $update = $true
    $curVersionFile = Join-Path $dirVersion "php_redis.txt"
    if(Test-Path $curVersionFile){
        $update = isNewVersion $ver (Get-Content $curVersionFile)
    }
    if($update){
        getStatic7zip
        $exe7z = Join-Path $dirShare "7z\7z.exe"
        foreach($v in $appInstall.Php){
            if("latest" -eq $v) { Continue }
            if(isNewVersion $v "7.5"){
                $dir = Join-Path $dirLocal ("php\" + $v + "\ext")
                if(Test-Path $dir){
                    echoStr ("更新" + $echoTab + "php_redis") $colorSuccess $true
                    echoStr ($echoTab + $ver + $echoTab) $colorSuccess 
                    $rsl = "php_redis-" + $ver + "-php-" + $v
                    $url = "https://github.com/dk-sirk/phpredis-windows-release/releases/download/"+ $rsl +"/" + $rsl +".7z"
                    if(!$isGithub){
                        $url = $proxyUrl + "?file=" + $url
                        $url = getWebContent $url $false
                    }
                    $save = Join-Path $dirDownload ($rsl + ".7z")
                    Invoke-WebRequest -Uri $url -UserAgent $userAgent -OutFile $save
                    #$args7z = "x -o"+$dirUnzip+" "+$save
                    #$exe7z x -o$dirUnzip $save
                    $args7z = ("x", "-y", ("-o`""+$dirUnzip+"`""), $save)
                    Start-Process $exe7z -ArgumentList $args7z
                    Start-Sleep -Seconds 2
                    Copy-Item -Path (Join-Path $dirUnzip ($rsl + "-nts.dll")) -Destination (Join-Path $dir "php_redis.dll") -Force
                }
            }
        }
        Set-Content -Path $curVersionFile -Value $ver -Force

    }
}

function getExtensionMemcached(){
    #https://github.com/lifenglsf/php_memcached_dll
    #https://github.com/lifenglsf/php_memcached_dll/raw/master/auto-master/basic/php7.4/nts_x64/libmemcached.dll
    #https://github.com/lifenglsf/php_memcached_dll/raw/master/auto-master/basic/php7.4/nts_x64/php_memcached.dll
    foreach($vl in $appInstall.Php){
        if("latest" -eq $vl) { Continue }
        if(isNewVersion $vl "7.3"){
            $dir =Join-Path $dirLocal ("php\" + $vl)
            if(Test-Path $dir){
                $dllLib = "https://github.com/lifenglsf/php_memcached_dll/raw/master/auto-master/basic/php"+$vl+"/nts_x64/libmemcached.dll"
                $dllExt = "https://github.com/lifenglsf/php_memcached_dll/raw/master/auto-master/basic/php"+$vl+"/nts_x64/php_memcached.dll"
                if(!$isGithub){
                    $dllLib = $proxyUrl + "?file=" + $dllLib
                    $dllExt = $proxyUrl + "?file=" + $dllExt
                    $dllLib = getWebContent $dllLib $false
                    $dllExt = getWebContent $dllExt $false
                }
                $saveLib = Join-Path $dir "libmemcached.dll"
                $saveExt = Join-Path $dir "ext\php_memcached.dll"
                if(!(Test-Path $saveLib) -or !(Test-Path $saveExt)){
                    echoStr ("更新" + $echoTab + "php_memcached") $colorSuccess $true
                    echoStr ($echoTab + $vl + $echoTab) $colorSuccess 
                    Invoke-WebRequest -Uri $dllLib -UserAgent $userAgent -OutFile $saveLib
                    Invoke-WebRequest -Uri $dllExt -UserAgent $userAgent -OutFile $saveExt
                }
            }
            
        }
    }
}

function getExtensionMongodb(){
    $urlHome = "https://github.com/mongodb/mongo-php-driver/"
    $ver = getGithubVersion ($urlHome + "tags")
    $curVersionFile = Join-Path $dirVersion "php_mongodb.txt"
    $update = $true
    if(Test-Path $curVersionFile){ $update = isNewVersion $ver (Get-Content $curVersionFile) }
    if($update){
        foreach($vl in $appInstall.Php){
            if("latest" -eq $vl) { Continue }
            if(isNewVersion $vl "7.4"){
                $dir = Join-Path $dirLocal ("php\" + $vl)
                if(Test-Path $dir){
                    $save = "php_mongodb-"+$ver+"-"+$vl+"-nts-x64.zip"
                    $dll = Join-Path $dirLocal ("php\"+$vl +"\ext\php_mongodb.dll")
                    $urlZip = "https://github.com/mongodb/mongo-php-driver/releases/download/"+$ver+"/" + $save
                    if(!$isGithub){
                        $urlZip = $proxyUrl + "?file="+$urlZip
                        $urlZip = getWebContent $urlZip $false
                    }
                    if(!(Test-Path $dll)){
                        echoStr ("更新" + $echoTab + "php_mongodb") $colorSuccess $true
                        echoStr ($echoTab + $ver + $echoTab) $colorSuccess

                        $save = Join-Path $dirDownload $save
                        Invoke-WebRequest -Uri $urlZip -UserAgent $userAgent -OutFile $save
                        Expand-Archive -Path $save -DestinationPath $dirUnzip -Force
                        Copy-Item -Path (Join-Path $dirUnzip "php_mongodb.dll") -Destination $dll -Force 
                        
                    }
                }
            }
        }
        Set-Content -Path $curVersionFile -Value $ver -Force
    }
}
######################################################################################################

function installApacheConf($dir){
    $httpConf = Join-Path $dir ("conf/httpd.conf")
    $targetConf = $httpConf.Replace($dirLocal, $dirConfig)
    $targetConf = $targetConf.Replace("\", "/").Replace("conf/", "/")
    if(!(Test-Path $targetConf)){
        $moduleExpire = '<IfModule mod_expires>
        ExpiresActive On
        ExpiresDefault "access plus 1 month"
        ExpiresByType text/html "access plus 1 months"
        ExpiresByType text/css "access plus 1 months"
        ExpiresByType image/gif "access plus 1 months"
        ExpiresByType image/jpeg "access plus 1 months"
        ExpiresByType image/jpg "access plus 12 months"
        ExpiresByType image/png "access plus 12 months"
        ExpiresByType image/webp "access plus 12 months"
        ExpiresByType image/avif "access plus 12 months"
        EXpiresByType application/x-javascript "access plus 12 months"
        EXpiresByType application/json "access plus 12 months"
        #EXpiresByType application/x-shockwave-flash "access plus 12 months"
        #ExpiresByType video/x-flv "access plus 12 months"
</IfModule>'
    
        $moduleSsl = 'SSLRandomSeed connect builtin
        Listen 127.0.0.1:443
        Listen [::]:443
        SSLStrictSNIVHostCheck off
        SSLCipherSuite AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL
        #SSLProtocol all -SSLv2 -SSLv3
        SSLProtocol all -SSLv2
        #SSLSessionCache        "dbm:d:/app/tmp/ssl_scache/ssl_scache_uashoe(512000)"'

        $etcDir = $targetConf.Replace("httpd.conf", "")
        $etcInclude = Join-Path $etcDir "conf.d/*.conf"
        $etcInclude = $etcInclude.Replace("\", "/").Replace("//", "/")
        $tempDir = $dir.Replace($dirLocal, $dirTemp)
        $tempDir = $tempDir.Replace("\", "/")
        $logDir = $dir.Replace($dirLocal, $dirLog)
        $logDir = $logDir.Replace("\", "/")
        $dir = $dir.Replace("\", "/")
        $etcDir = $etcDir.Replace("\", "/")
        $editConfig = (
            ("Define SRVROOT `"c:/Apache24`"", ("Define SRVROOT `""+$dir+"`"")),
            ("Listen 80", "Listen 127.0.0.1:80`n[::]:80"),
            ("<IfModule unixd_module>", "`n`nLoadModule fcgid_module modules/mod_fcgid.so`n`n`n<IfModule unixd_module>"),
            ("#DocumentRoot `"${SRVROOT}/htdocs`"", ""),
            ("#<Directory `"${SRVROOT}/htdocs`">", ""),
            ("    Options Indexes FollowSymLinks", "    Options ExecCGI Indexes Includes FollowSymLinks"),
            ("ErrorLog `"logs/error.log`"", ("ErrorLog `"" + (Join-Path $logDir "error.log").Replace("\", "/") + "`"")),
            ("    CustomLog `"logs/access.log`" common", ("    CustomLog `"" + (Join-Path $logDir "custom.log").Replace("\", "/") + "`" common")),
            ("#    ScriptAlias /cgi-bin/ `"${SRVROOT}/cgi-bin/`"", ""),
            ("#<Directory `"${SRVROOT}/cgi-bin`">", ""),
            ("<IfModule mime_module>", ($moduleExpire + "`n`n<IfModule mime_module>")),
            ("#Include conf/extra/httpd-mpm.conf", "Include conf/extra/httpd-mpm.conf"),
            ("#Include conf/extra/httpd-vhosts.conf", ("#Include conf/extra/httpd-vhosts.conf`nInclude "+$etcInclude)),
            ("SSLRandomSeed connect builtin", $moduleSsl)
        )
        $phpDir = Join-Path $dirLocal ("php\" + $defaultPhpVersion)
        $phpDir = $phpDir.Replace("\", "/")
        $append = "`n`n`n# PHP-Module setup`n#PHPIniDir `""+$phpDir+"`"`n"
        foreach($lib in ("php7ts.dll", "libpq.dll", "ssleay32.dll", "libsasl.dll", "leveldb.dll")){
            $append += ("#LoadFile `""+$phpDir+"/"+$lib + "`"`n")
        }
        $append += ("#LoadModule php8_module `""+$phpDir+"/php8apache2_4.dll`"`n#`n")
        $append += ("# Fcgid setup`n<IfModule fcgid_module>`n    AddHandler fcgid-script .fcgi .php`n    AddType application/x-httpd-php .php`n`n")
        $append += ("    # 设置PHP_FCGI_MAX_REQUESTS大于或等于FcgidMaxRequestsPerProcess, 防止php-cgi进程在处理完所有请求前退出`n")
        $append += ("    FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 500`n    #php-cgi每个进程的最大请求数`n    FcgidMaxRequestsPerProcess 500`n")
        $append += ("    #php-cgi最大的进程数`n    FcgidMaxProcesses 5`n    #最大执行时间`n    FcgidIOTimeout 1800`n    FcgidIdleTimeout 1800`n")
        $append += ("    FcgidBusyScanInterval 1800`n    FcgidBusyTimeout 1800`n    FcgidErrorScanInterval 1800`n    FcgidIdleScanInterval 1800`n")
        $append += ("    FcgidIdleTimeout 1800`n    FcgidProcessLifeTime 3600`n    FcgidZombieScanInterval 1800`n")
        $append += ("    #上传文件的最大尺寸 100MB`n    FcgidMaxRequestLen 104857600`n`n")
        $append += ("    #FcgidInitialEnv PATH `"C:/php;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;C:/Apache2/bin;`"`n")
        $append += ("    FcgidInitialEnv SystemRoot `"C:/Windows`"`n    FcgidInitialEnv windir `"C:/Windows`"`n")
        $append += ("    FcgidInitialEnv SystemDrive `"C:`"`n    FcgidInitialEnv TEMP `""+$tempDir+"`"`n    FcgidInitialEnv TMP `""+$tempDir+"`"`n`n")
        $append += ("    #php.ini的存放目录`n    FcgidInitialEnv PHPRC `""+(Join-Path $phpDir "php.ini").Replace("\", "/")+"`"`n`n")
        $append += ("    #php-cgi的路径`n    FcgidWrapper `""+(Join-Path $phpDir "php-cgi.exe").Replace("\", "/")+"`" .php`n")
        $append += "</IfModule>"
    
        $config = $newConfig = @()
        foreach($cs in $editConfig){
            $config += $cs[0]
            $newConfig += $cs[1]
        }
        $read = readFileLines $httpConf
        $content = ""
        foreach($line in $read){
            if($config.Contains($line)){
                $index = $config.IndexOf($line)
                $line = $newConfig[$index]
            }
            $content += $line + "`n"
        }

        echoStr "初始化 " $null $true
        echoStr ($echoTab + "Apache" + $echoTab) $colorThird $true
        echoStr $targetConf $colorSuccess
        Set-Content -Path $targetConf -Value ($content + $append) -Force
    }
}

function initApacheConf(){
    $dir = Join-Path $dirLocal "apache"
    $dl = Get-ChildItem -Path $dir
    foreach($dr in $dl){
        $dr = Join-Path $dir $dr.Name.ToString()
        if(Test-Path (Join-Path $dr "bin/httpd.exe")){
            installApacheConf $dr
        }
    }
}

function installMysqlIni($result, $mariadb = $false){
    if($mariadb) {
        $str = "mariadb"
    }else{
        $str = "mysql"
    }
    $my = Join-Path $result.InstallDir "my.ini"
    $etcMy = $my.Replace($dirLocal, $dirConfig)
    if(!(Test-Path $my)){
        $str = $str + $result.Label.Replace(".", "")
        $content = "[mysqld]`n# innodb_buffer_pool_size = 128M`n`n"
        $content += "basedir = `"" + $result.InstallDir + "`"`n"
        $content += "datadir = `"" + $result.DbDir + "`"`n"
        $content += "tmpdir = `"" + $result.TempDir + "`"`n`n"
        $content += "# join_buffer_size = 128M`n# sort_buffer_size = 2M`n# read_rnd_buffer_size = 2M`n`n"
        $content += "#bind-address = 127.0.0.1, 172.16.0.1`nport = 3306`n`n"
        $content += "plugin_dir = `"" + (Join-Path $result.InstallDir "lib\plugin") + "`"`n`n"
        $content += "socket = `"" + (Join-Path $dirRun ("mysql"+$result.Label.Replace(".", "") + ".sock")) + "`"`n"
        $content += "pid-file = `"" + (Join-Path $dirRun ("mysql"+$result.Label.Replace(".", "") + ".pid")) + "`"`n"
        $content += "log-error = `"" + (Join-Path $result.LogDir "error.txt") + "`"`n`n"
        $cotnent += "#collation_server=utf8_general_ci`ncollation_server=utf8mb4_unicode_ci`ncharacter_set_server=utf8mb4`n`nlocal_infile=1`n`n"
        $content += "#sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES`n#sql_mode=IGNORE_SPACE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION`n`n"
        $content += "symbolic-links=0`n`n"
        $content += "max_connections=200`nmax_connect_errors=10`n`n"
        $content += "#server_id = `n#log_bin="+ ($str+"-bin.log") + "`n#binlog_format=mixed`n`n"
        $content += "#default_authentication_plugin=mysql_native_password`n`n`n"
        $content += "[mysql]`n#user=`n#password=`ndefault-character-set=utf8mb4`n`n`n"
        $content += "[client]`n#user=`n#password=`n#port=`ndefault-character-set=utf8mb4`n`n`n"
        $cotnent += "[mysqldump]`n#user=`n#password=`nquick`nmax_allowed_packet = 256M`ndefault-character-set=utf8mb4`n"
        Set-Content -Path $my -Value $content -Force
        Copy-Item -Path $my -Destination $etcMy -Force
    }
}

function initMysqlIni(){
    foreach($vl in $appData.Mysql.Update.Keys){
        if(Test-Path (Join-Path $appData.Mysql.Update.$vl.InstallDir "bin/mysql.exe")){
            echoStr "初始化 " $null $true
            echoStr ($echoTab + "Mysql" + $echoTab) $colorOfficial $true
            echoStr (Join-Path $appData.Mysql.Update.$vl.InstallDir "my.ini") $colorSuccess
            installMysqlIni $appData.Mysql.Update.$vl
        }
    }
    foreach($vl in $appData.Mariadb.Update.Keys){
        if(Test-Path (Join-Path $appData.Mariadb.Update.$vl.InstallDir "bin/mysql.exe")){
            echoStr "初始化 " $null $true
            echoStr ($echoTab + "Mariadb" + $echoTab) $colorOfficial $true
            echoStr (Join-Path $appData.Mariadb.Update.$vl.InstallDir "my.ini") $colorSuccess
            installMysqlIni $appData.Mariadb.Update.$vl $true
        }
    }
}

function installPhpIni($result){
    $resConfDir = $result.ConfDir
    if($resConfDir.Length -le 0){ $result.ConfDir = $result.InstallDir.Replace($dirLocal, $dirConfig) }

    $logFile = Join-Path $result.LogDir "error.txt"
    $mailLogFile = Join-Path $result.LogDir "mail.error.txt"
    $exeSendmail = Join-Path $dirShare ("sendmail\sendmail.exe")
    $extDir = Join-Path $result.InstallDir "ext"
    $cafile = Join-Path $dirConfig ("ssl\"+$curlCaFile)
    $tmpDir = $result.TempDir
    $uploadDir = $result.UploadDir
    $sessionDir = $result.SessionDir
    createDirectory $uploadDir
    createDirectory $sessionDir

    $iniPhp = Join-Path $result.InstallDir "php.ini"
    $iniConf = Join-Path $result.ConfDir "php.ini"
    $iniDefault = Join-Path $result.InstallDir "php.ini-development"
    if(!(Test-Path $iniPhp)){
        if(Test-Path $iniConf){
            echoStr "复制 php.ini" $null $true
            echoStr ($echoTab + $iniConf +$echoTab) $colorNormal $true
            echoStr $iniPhp $colorSuccess
            Copy-Item -Path $iniConf -Destination $iniPhp -Force
        }else{
            echoStr "初始化" $null $true
            echoStr ($echoTab + "php.ini" + $echoTab) $colorProgress $true
            echoStr ($echoTab + $iniDefault +$echoTab) $colorSuccess
            $config = $newConfig = @()

            $editConfig = (
                ("disable_functions =", "disable_functions ="),
                ("max_execution_time = 30", "max_execution_time = 300"),
                ("memory_limit = 128M", "memory_limit = 128M"),
                (";error_log = syslog", (";error_log = syslog`nerror_log = `""+$logFile + "`"")),
                ("post_max_size = 8M", "post_max_size = 32M"),
                (";extension_dir = `"ext`"", (";extension_dir = `"ext`"`nextension_dir = `"" + $extDir + "`"")),
                (";cgi.fix_pathinfo=1", ";cgi.fix_pathinfo=1`ncgi.fix_pathinfo=1"),
                (";sys_temp_dir = `"/tmp`"", (";sys_temp_dir = `"/tmp`"`nsys_temp_dir = `"" + $tmpDir + "`"")),
                (";upload_tmp_dir =", (";upload_tmp_dir =`nupload_tmp_dir =`"" + $uploadDir + "`"")),
                ("upload_max_filesize = 2M", "upload_max_filesize = 100M"),
                ("max_file_uploads = 20", "max_file_uploads = 60"),
                (";user_agent=`"PHP`"", (";user_agent=`"PHP`"`nuser_agent=`""+$userAgent+"`"")),
                (";date.timezone =", ";date.timezone =`ndate.timezone = PRC"),
                (";sendmail_path =", (";sendmail_path =`nsendmail_path =`"\`"" + $exeSendmail + "\`" -t`"")), 
                (";mail.log = syslog", (";mail.log = syslog`nmail.log = `"" + $maillogFile + "`"")),
                (";session.save_path = `"/tmp`"", (";session.save_path = `"/tmp`"`nsession.save_path = `"" + $sessionDir + "`"")),
                (";curl.cainfo =", (";curl.cainfo =`ncurl.cainfo = `"" + $cafile + "`"")),
                (";openssl.cafile=", (";openssl.cafile=`nopenssl.cafile=`"" + $cafile + "`"")),
                (";extension=bz2", "extension=bz2"),
                (";extension=curl", "extension=curl"),
                (";extension=ffi", "extension=ffi"),
                (";extension=ftp", "extension=ftp"),
                (";extension=fileinfo", "extension=fileinfo"),
                (";extension=gd", "extension=gd"),
                (";extension=gd2", "extension=gd2"),
                (";extension=gettext", "extension=gettext"),
                (";extension=gmp", "extension=gmp"),
                (";extension=intl", "extension=intl"),
                (";extension=imap", "extension=imap"),
                (";extension=mbstring", "extension=mbstring"),
                (";extension=exif", "extension=exif"),
                (";extension=mysqli", "extension=mysqli"),
                (";extension=openssl", "extension=openssl"),
                (";extension=pdo_mysql", "extension=pdo_mysql"),
                (";extension=pdo_pgsql", "extension=pdo_pgsql"),
                (";extension=pdo_sqlite", "extension=pdo_sqlite"),
                (";extension=pgsql", "extension=pgsql"),
                (";extension=soap", "extension=soap"),
                (";extension=sockets", "extension=sockets"),
                (";extension=sodium", "extension=sodium"),
                (";extension=sqlite3", "extension=sqlite3"),
                (";extension=tidy", "extension=tidy"),
                (";extension=xsl", "extension=xsl"),
                (";extension=zip", "extension=zip`n`n`n;luinfy start`nextension=redis`nextension=memcached`nextension=mongodb`n;luinfy end`n`n")
            )

            foreach($cs in $editConfig){
                $config += $cs[0]
                $newConfig += $cs[1]
            }

            $read = readFileLines $iniDefault
            $content = ""
            foreach($line in $read){
                if($config.Contains($line)){
                    $index = $config.indexOf($line)
                    $line = $newConfig[$index]
                }
                $content += $line + "`n"
            }

            Set-Content -Path $iniPhp -Value $content -Force
            Copy-Item -Path $iniPhp -Destination $iniConf -Force
        }
    } 
}

function initPhpIni(){
    foreach($vl in $appData.Php.Update.Keys){
        installPhpIni $appData.Php.Update.$vl
    }
    foreach($vl in $appData.PhpBuild.Update.Keys){
        $iniPhp = Join-Path $appData.PhpBuild.Update.$vl.InstallDir "php.ini"
        $iniConf = $iniPhp.Replace($dirLocal, $dirConfig)
        if(!(Test-Path $iniPhp) -and (Test-Path $iniConf)){
            echoStr "复制 php.ini" $null $true
            echoStr ($echoTab + $iniConf +$echoTab) $colorNormal $true
            echoStr $iniPhp $colorSuccess
            Copy-Item -Path $iniConf -Destination $iniPhp -Force
        }
    }
}

function stopService(){
    foreach($name in $installedService){
        if("running" -eq $installedService.$name){
            echoStr "停止服务" $null $true
            echoStr ($echoTab + $name +$echoTab) $colorNormal
            Stop-Service -Name $name
        }
    }
}

function killProcess(){
    $apps = ("php-cgi", "mysql", "mariadb", "psql", "redis", "pg_ctl", "postgres")
    foreach($app in $apps){
        try{
            Get-Process ($app+"*") | Stop-Process
            echoStr "停止进程" $null $true
            echoStr ($echoTab + $app + $echoTab) $colorNormal
        }catch{
        }
    }
}

function backupConfigFile($resDir, $desDir, $file){
    if($resDir.Contains($dirConfigBackup)){
        $method = "恢复"
        $isBackup = $false
    }else{
        $method = "备份"
        $isBackup = $true
    }
    $resFile = Join-Path $resDir $file
    $desFile = Join-Path $desDir $file
    if(Test-Path $resFile){
        if(!(Test-Path $desDir)) { New-Item -Path $desDir -ItemType Directory | Out-Null }
        $ext = $file.Split('.')[1]
        $extLen = $ext.Length
        echoStr $method $null $true
        echoStr ($echoTab + $resFile + $echoTab) $colorNormal $true
        echoStr "到" $null $true
        echoStr ($echoTab + $desFile + $echoTab) $colorOfficial
        if(Test-Path $desFile){
            $newName = $desFile.Substring(0, $desFile.Length - $extLen) + $now + "." + $ext
            Rename-Item -Path $desFile -NewName $newName
        }
        
        Copy-Item -Path $resFile -Destination $desFile -Force

        if($isBackup){
            $confFile = $desFile.Replace($dirConfigBackup, $dirConfig)
            if(!(Test-Path $confFile)){
                Copy-Item -Path $resFile -Destination $confFile -Force
            }
        }
    }
}

function backupConfig(){
    echoStr ("检查是否需要备份程序的配置文件") $colorSuccess
    foreach($vk in $appData.Keys){
        if($appConfFile.ContainsKey($vk)){
            #echoStr ("开始备份 "+$vk + " 的配置文件") $colorSuccess
            foreach($vl in $appData.$vk.Update.Keys){
                $resDir = $appData.$vk.Update.$vl.ConfDir
                $desDir = $appData.$vk.Update.$vl.BackupDir
                #echoStr ($vk + $echoTab + $resDir +$echoTab +$desDir) $colorThird
                if((Test-Path $resDir) -and (Test-Path $desDir)){
                    backupConfigFile $resDir $desDir $appConfFile.$vk
                }
                #backupConfigFile $appData.$vk.Update.$vl.ConfDir $appData.$vk.Update.$vl.BackupDir $appConfFile.$vk
            }
            #echoStr ("成功备份 "+$vk + " 的配置文件") $colorSuccess
        }
    }
}
function restoreConfig(){
    echoStr ("检查是否需要恢复程序的配置文件") $colorSuccess
    foreach($vk in $appData.Keys){
        if($appConfFile.ContainsKey($vk)){
            #echoStr ("开始恢复 "+$vk + " 的配置文件") $colorSuccess
            foreach($vl in $appData.$vk.Update.Keys){
                $resDir = $appData.$vk.Update.$vl.BackupDir
                $desDir = $appData.$vk.Update.$vl.ConfDir
                if((Test-Path $resDir) -and (Test-Path $desDir)){
                    backupConfigFile $resDir $desDir $appConfFile.$vk
                }
                #backupConfigFile $appData.$vk.Update.$vl.BackupDir $appData.$vk.Update.$vl.ConfDir $appConfFile.$vk
            }
            #echoStr ("成功恢复 "+$vk + " 的配置文件") $colorSuccess
        }
    }
}

function getUpdate(){
    echoStr "开始进行更新前准备...."
    echoWaitProgress $colorWarning "*"
    stopService
    killProcess
    echoWaitProgress $colorWarning "*"

    echoStr "开始检查程序是否存在新版本...." $colorSuccess
    #
    foreach($app in $appInstall.Keys){
        if(!("GraalvmJdk", "Graalvm", "PhpBuild", "Ffmpeg", "Putty", "Composer").Contains($app)){
            echoAppCheck $app
            &("getLatest" + $app)
        }
    }

    #后台下载进程
    do{
        Start-Sleep -Seconds $checkDownloadSecond
        $removeTid = @()
        foreach($v in $downloadAsync.Keys){
            if($downloadAsync.$v.Result.IsCompleted -eq $true){
                $removeTid += $v
                echoStr "下载完成:" $null $true
                $str = $echoTab + $downloadAsync.$v.App + " " + $downloadAsync.$v.Version + $echoTab + $downloadAsync.$v.saveFile + $echoTab
                echoStr $str $downloadAsync.$v.Official $true
                echoStatus $true
            }else{
                $size = getSize $downloadAsync.$v.saveFile
                echoStr ($downloadAsync.$v.App + " " + $downloadAsync.$v.Version + $echoTab) $null $true
                echoDownloadProgress $size $downloadAsync.$v.Size
            }
        }
    
        foreach($v in $removeTid){
            $downloadAsync.$v.Psh.Dispose()
            $downloadAsync.$v.Job.Dispose()
            $downloadAsync.Remove($v)
        }
    }while($downloadAsync.Keys.Count -gt 0)
    Write-Host "`n`n"

    backupConfig
    echoWaitProgress $colorWarning "*"

    #解压缩,安装
    foreach($app in $appInstall.Keys){
        $update = $appData.$app.Update
        if($update.Length -le 0){ Continue }
        $str = $app.ToLower()
        $official = isOfficial $app
        #echoWaitProgress $colorSuccess "*"
        echoStr "开始更新 " $colorSuccess $true
        echoStr ($echoTab + $app + $echoTab) $official
        foreach($vl in $update.Keys){
            $data = $update.$vl
            echoStr "安装" $colorError $true
            echoStr ($echoTab + $app + $echoTab) $colorError $true
            echoStr $data.Version $colorSuccess
            try{
                Remove-Item -Path (Join-Path $data.InstallDir "*") -Recurse -Force
            }catch{}

            if("cloudflared" -eq $str){
                echoStr "重命名" $null $true
                echoStr ($echoTab + $data.SaveFile + $echoTab) $colorNormal
                Copy-Item -Path $data.SaveFile -Destination (Join-Path $data.InstallDir "cloudflared.exe") -Force
            }else{
                echoStr "解压缩" $null $true
                echoStr ($echoTab + $data.SaveFile + $echoTab) $colorNormal
                Expand-Archive -Path $data.SaveFile -DestinationPath $dirUnzip -Force
    
                if($str.Contains("jdk") -or $str.Contains("graalvm")){
                    $unzip = (Get-ChildItem $dirUnzip -Filter "*jdk*").Name.ToString()
                    $unzip = Join-Path $dirUnzip $unzip
                    Move-Item -Path (Join-Path $unzip "*") -Destination $data.InstallDir -Force
                }else{
                    Move-Item -Path $data.Unzip -Destination $data.InstallDir -Force
                }
                echoStr "复制新文件到" $null $true
                echoStr ($echoTab + $data.InstallDir + $echoTab) $colorNormal
            }

            $vf = getVersionFile $app $data
            Set-Content -Path $vf -Value $data.Version -Force
            Remove-Item (Join-Path $dirUnzip "*") -Recurse

            if("unbound" -eq $str){
                $save = Join-Path $data.InstallDir "named.cache"
                Invoke-WebRequest -Uri "https://www.internic.net/domain/named.cache" -UserAgent $userAgent -OutFile $save
            }
        }

        createCmdLink $app
        echoStr "成功更新 " $colorSuccess $true
        echoStr ($echoTab + $app + $echoTab) $official
        echoWaitProgress $colorWarning "+"
    }

    #安装后处理
    Write-Host "`n"
    echoStr "准备进行更新后工作...."
    initPipMirror
    initVimrc
    initRunAsRoot
    deletePgAdmin
    deleteDevelFiles
    getLatestCacertificate
    
    echoWaitProgress $colorWarning "*"
    restoreConfig
    echoWaitProgress $colorWarning "*"
    
    initApacheConf
    initMysqlIni
    initPhpIni
    #echoWaitProgress $colorWarning "*"
    getStaticSendmail
    getStatic7zip
    Write-Host "`n`n"
    getExtensionRedis
    getExtensionMemcached
    getExtensionMongodb

    echoWaitProgress $colorWarning "*"
    getLatestModFcgid
    getLatestComposer
    getLatestFfmpeg
    getLatestPutty
    echoStr "所有项目更新完毕." $colorSuccess
}


getUpdate